#include "imagejpeg.h"

ImageJpeg::ImageJpeg(const QString& fn ) :
   Image(fn)
{
    scanlines = NULL;
    last_buffered_scanline = n_buffered_scanlines = 0;
    image_width = image_height = 0;
    pFile = NULL;
    extra_zoom = 1;
    read_cinfo = jpeg_decompress_struct();
    write_cinfo = jpeg_compress_struct();
    jerr = jpeg_error_mgr();
}

ImageJpeg::~ImageJpeg()
{
    if ( reading )
    {   // Call finish only if we have read the entire image
        if ( read_cinfo.output_scanline >= read_cinfo.output_height )
            jpeg_finish_decompress( &read_cinfo );
        jpeg_destroy_decompress( &read_cinfo );
        for (int s = 0; s < n_buffered_scanlines; s++ )
            delete [] scanlines[s];
        delete [] scanlines;
        fclose(pFile);
    }
    if ( writing )
    {   // Call finish only if we have written the entire image
        if ( write_cinfo.next_scanline >= write_cinfo.image_height )
            jpeg_finish_compress(&write_cinfo);
        jpeg_destroy_compress( &write_cinfo );
        fclose(pFile);
    }
}

bool ImageJpeg::startReading( int zoom, int& output_width, int& output_height )
{
    if ( writing || reading )
        return false;

    // Open the image file
    if ( (pFile = fopen( filename.toAscii(), "rb" )) == NULL )
        return false;

    // Calculate adeguate zoom factor:
    switch (zoom)
    {
    case 256:
    case 128:
    case 64:
    case 32:
    case 16:
        // Calculate our requested "extra" zoom over the libjpeg provided maximum zoom
        extra_zoom = zoom/8;
        zoom = 8;
        break;
    case 8:
    case 4:
    case 2:
    case 1:
        // No extra_zoom factor, libjpeg already provides enough internal zooming!
        extra_zoom = 1;
        break;
    default:
        // Quit on invalid zoom factors
        return false;
    }

    // initialize jibjpeg data structure
    read_cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_decompress(&read_cinfo);

    // Set file in libjpeg
    jpeg_stdio_src(&read_cinfo, pFile );

    // Read header
    jpeg_read_header(&read_cinfo, TRUE);

    // Configure the decoder to use FAST dct
    read_cinfo.dct_method = JDCT_FASTEST;
    // Shrink scanline to the best possible libjpeg supported value (1,2,4,8 only)
    read_cinfo.scale_denom = zoom;
    // Select a faster upsampling
    read_cinfo.do_fancy_upsampling = false;
    // Initialize the decompressor, so we know our output dimensions post-scaling
    jpeg_start_decompress(&read_cinfo);

    // Now, allocate the temporary buffer to hold scanlines:
    n_buffered_scanlines = read_cinfo.rec_outbuf_height;
    scanlines = new JSAMPLE*[ n_buffered_scanlines ];
    for (int r = 0; r < n_buffered_scanlines; r++ )
        scanlines[r] = new JSAMPLE[ read_cinfo.output_width*read_cinfo.output_components ];
    // Force last_buffered_scanline to be invalid, this will force the first read later on
    last_buffered_scanline = n_buffered_scanlines+1;
    image_width = output_width = read_cinfo.output_width / extra_zoom;
    image_height = output_height = read_cinfo.output_height / extra_zoom;
    reading = true;
    return true;
}

bool ImageJpeg::startWriting( int width, int height )
{
    if ( reading || writing )
        return false;
    // Refuse to create an empty image...
    if ( (width <= 0) ||
         (height <= 0) )
        return false;

    // Open the image file
    if ( (pFile = fopen( filename.toAscii(), "wb" )) == NULL )
        return false;

    // initialize jibjpeg data structure
    write_cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&write_cinfo);
    // Set file in libjpeg
    jpeg_stdio_dest(&write_cinfo, pFile);

    // Setup image informations
    write_cinfo.image_width = width;
    write_cinfo.image_height = height;
    write_cinfo.input_components = 3;
    write_cinfo.in_color_space = JCS_RGB;

    // Set defaults then reduce for speed
    jpeg_set_defaults(&write_cinfo);
    // 40 was too low, 85 seems better...
    jpeg_set_quality (&write_cinfo, 80, false);
    write_cinfo.dct_method = JDCT_FASTEST;

    // Start compression
    jpeg_start_compress(&write_cinfo, TRUE);

    writing = true;
    return true;
}

uchar* ImageJpeg::readScanline()
{
    if ( !reading )
        return NULL;

    // Initialize the return value to be NULL (error or read at the end of the image)
    uchar* ret = NULL;
    // Read scanlines until we reach the proper extra_zoom factor.
    // Basically, we "skip" extra_zoom-1 lines. We could do better here and do some "smoothing" between
    // the discarded lines... TODO!
    for ( int z = 0; z < extra_zoom; z++ )
    {
        // First we iterate on already buffered scanlines (if any)
        if ( last_buffered_scanline < n_buffered_scanlines )
        {
            ret = scanlines[ last_buffered_scanline++ ];
        }
        // Otherwise, ask jpeglib to buffer some more scanlines from the jpeg file
        else if ( read_cinfo.output_scanline < read_cinfo.output_height )
        {
            jpeg_read_scanlines( &read_cinfo, scanlines, read_cinfo.rec_outbuf_height );
            last_buffered_scanline = 1;
            ret = scanlines[0];
        }
        // Or quit if we have already read the entire image
        else
            break;
    }
    // Remember, if extra_zoom > 1 we also need to "compress" the scanline. Again, here we could use
    // some kind of nice smoothing... TODO!
    if ( (ret != NULL) && (extra_zoom > 1) )
    {
        // we need to "compress" the scanline to respect the extra_zoom factor
        for (int x = 0; x < image_width; x++ )
        {
            int dst = x*COLOR_CHANNELS;
            int src = x*COLOR_CHANNELS*extra_zoom;
            ret[ dst ] = ret[ src ];
            ret[ dst+1 ] = ret[ src+1 ];
            ret[ dst+2 ] = ret[ src+2 ];
        }
    }
    return ret;
}

bool ImageJpeg::writeScanline( uchar* row)
{
    if ( writing && ( write_cinfo.next_scanline < write_cinfo.image_height ) )
    {
        jpeg_write_scanlines(&write_cinfo, &row, 1);
        return true;
    }
    return false;
}

bool ImageJpeg::readImage(QImage *dest_buffer, const QRect& source_rect, const QRect& dest_rect, int zoom )
{
    // Reset image to empty...
    dest_buffer->fill(0);

    int source_width,
        source_height;
    int dest_width = dest_buffer->width(),
        dest_height = dest_buffer->height();
    int extra_cols = 0;

    if ( !startReading(zoom, source_width, source_height ) )
        return false;

    // quit if size differs
    if ( source_width > dest_width )
        source_width = dest_width;
    if ( source_height > dest_height )
        source_height = dest_height;
    if ( source_width < dest_width )
        extra_cols = dest_width - source_width;

    uchar* tmp;
    // Get pointer to dest buffer and place it to the beginning of the
    // first pixel do te be copied
    uchar* dest_ptr = dest_buffer->bits();
    // Prescale width
    int byte_width = source_width*3;
    // Prescale extra_cols
    extra_cols *= 3;
    // Read all the interesting rows...
    for ( int row = 0; row < source_height; row++ )
    {
        if ( (tmp = readScanline()) != NULL )
        {
            memcpy( dest_ptr, tmp, byte_width );
            dest_ptr = &dest_ptr[byte_width + extra_cols];
      /*    This code is only for conversion to RGB16
           for ( int col = 0; col < source_width; col++ )
           {
                red = tmp[0];
                green = tmp[1];
                blue = tmp[2];
                // We always assume dest_ptr to be where this pixel
                // needs to be copied and convert colors to 16bit 565 format
                *dest_ptr = (red>>3)<<11 | // red=5bits
                            (green>>2)<<5  | // green=6bits
                            (blue>>3); // blue=5bits
                // increment dest_ptr to next byte
                dest_ptr = &dest_ptr[1];
                // move scanline to next pixel
                tmp = &tmp[3];
           }
           dest_ptr = &dest_ptr[extra_cols];*/
       }
        else
            return false;
    }
    return true;
}

bool ImageJpeg::isValid()
{
    struct jpeg_decompress_struct local_cinfo;
    struct jpeg_error_mgr local_jerr;

    FILE* File = fopen( filename.toAscii(), "rb");
    if ( File == NULL )
        return false;

    // initialize jibjpeg data structure
    local_cinfo.err = jpeg_std_error(&local_jerr);
    jpeg_create_decompress(&local_cinfo);
    // Set file in libjpeg
    jpeg_stdio_src(&local_cinfo, File );
    // Read header
    jpeg_read_header(&local_cinfo, TRUE);
    image_width = local_cinfo.image_width;
    image_height = local_cinfo.image_height;
    jpeg_destroy_decompress( &local_cinfo );
    fclose( File );
    return ( image_width > 0 ) && ( image_height > 0 );
}

bool ImageJpeg::writeImage( const QImage& )
{
    return false;
}

